/**
* 连接wifi获取北京时间并显示在OLED上
*/

// 引入U8g2图形库，用于驱动OLED显示屏
#include <U8g2lib.h>
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>

// 定义SPI接口上OLED模块的相关引脚
// OLED片选信号（Chip Select）连接至D0引脚
#define OLED_CS D0
// 数据/命令选择信号（Data/Command）连接至D1引脚
#define OLED_DC D1
// OLED复位信号（Reset）连接至D2引脚（可选，如果没有物理连接复位引脚，则不需要此定义）
#define OLED_RST D2

// 创建一个U8g2对象实例，指定OLED型号为SSD1306，分辨率为128x64，采用4线软件SPI模式及对应的引脚配置
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ D5, /* data=*/ D7, /* cs=*/ OLED_CS, /* dc=*/ OLED_DC, /* reset=*/ OLED_RST);

// 定义wifi名称
const char *ssid     = "DingDang";
// 定义wifi密码
const char *password = "dd5.xw5.ly3.xs12..";
// WiFiUDP
WiFiUDP ntpUDP;
// 初始化NTPClient
NTPClient timeClient(ntpUDP);

// 设置函数：初始化串口通信以及OLED模块相关引脚
void setup() {
  // 初始化Serial串口通信，波特率为115200
  Serial.begin(115200);

  // 将OLED的片选和数据/命令选择引脚设为输出模式
  pinMode(OLED_CS, OUTPUT);
  pinMode(OLED_DC, OUTPUT);

  // 若OLED有连接复位引脚，则对其进行初始化操作
  if (OLED_RST != NOT_A_PIN) {
    pinMode(OLED_RST, OUTPUT);

    // 先将复位引脚拉高，然后短暂拉低以完成OLED模块的硬件复位
    digitalWrite(OLED_RST, HIGH);
    delay(100);
    digitalWrite(OLED_RST, LOW);
    delay(100);
    digitalWrite(OLED_RST, HIGH);
  }

  // 开始初始化U8g2库与OLED显示屏之间的通信
  u8g2.begin();

  // 开始初始化wifi连接  
  WiFi.begin(ssid, password);
  // 等待连接  
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }
  
  // wifi连接成功，初始化时间客户端  
  timeClient.begin();
}

// 循环函数：在此函数内编写循环执行的代码，如更新OLED显示内容
void loop() {
  // 清除OLED显示缓存
  u8g2.clearBuffer();
  // 绘制时间
  drawYmdhms();
  // 绘制电量
  drawBattery();
  // oled显示
  u8g2.sendBuffer();

  // 延迟2秒后再进行下一轮显示更新
  delay(1000);
}

/******************************* 渲染函数 START *******************************/
// 绘制时间
void drawYmdhms() {
  // 设置字体为ncenB14_tr样式
  u8g2.setFont(u8g2_font_ncenB08_tr);
  // 获取当前时间  
  time_t localTime = getLocalTime(timeClient, 8);
  // 定义时间buffer
  char timeBuffer[20];
  // 格式化时间
  formatDateTime(localTime,timeBuffer,sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S");
  // 绘制年月日时分秒 
  u8g2.drawStr(5, 15, timeBuffer);
}

/**
 * 绘制电量
 * @property {char} count - 电量剩余  0-5：0无电量  5满电量
*/
void drawBattery() {
  // 绘制电量
  u8g2.setFont(u8g2_font_battery19_tn);
  u8g2.drawStr(110, 20, "5");
}
/******************************* 渲染函数 E N D *******************************/

/******************************* 工具函数 START *******************************/
/**
 * 自定义格式化函数，格式化后的结果会覆盖第二个参数buffer
 * @property {time_t} t - 时间对象
 * @property {char*} buffer - 时间对象的buffer
 * @property {size_t} bufferSize - 事件对象buffer的大小
 * @property {char*} formatStr - 格式化字符串
*/
void formatDateTime(time_t t, char* buffer, size_t bufferSize, const char* formatStr) {
  struct tm timeinfo;
  // 转换为本地时间
  localtime_r(&t, &timeinfo); 
  // 时间格式化
  strftime(buffer, bufferSize, formatStr, &timeinfo);
}

/**
 * 获取本地时间
 * @property {NTPClient} ntpClient - NTP客户端
 * @property {int} timeZoneOffsetHours - 时区偏移量，中国大陆偏移8小时
 * @return {time_t} 返回时间对象
*/
time_t getLocalTime(NTPClient ntpClient, int timeZoneOffsetHours){
  // 时间更新
  ntpClient.update();
  // 获取UTC时间戳
  time_t utcTime = ntpClient.getEpochTime(); 
  // 将UTC时间转换为本地时间
  struct tm *timeInfo = gmtime(&utcTime);
  // 增加时区偏移小时数
  timeInfo->tm_hour += timeZoneOffsetHours; 
  // 转换回Unix时间戳（此时已经是调整过时区后的本地时间戳）
  return mktime(timeInfo);
}
/******************************* 工具函数 E N D *******************************/
